﻿using SCG.General;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

//Inputs - A DungeonTheme, A Dungeon that contains the levels above this one
//Output - A DungeonLevel - automatically appended to the Dungeon room list

namespace TTEngine
{
    public class DungeonLevelBuilder
    {
        //controls
        private const int MAX_DUNGEON_SIZE = 800;
        private const int STARTING_SPACE = 10; 

        //constructor
        public DungeonLevelBuilder(DungeonTheme theme, Dungeon dungeon)
        {
            Theme = theme;
            Dungeon = dungeon;
        }

        public DungeonTheme Theme { get; set; }
        public Dungeon Dungeon { get; set; }

        //Generate a level given a theme
        public DungeonLevel BuildLevel(IDungeonHost host)
        {
            DungeonLevel level = null;
            bool success = false;
            while (!success)
            {
                success = true;

                level = new DungeonLevel();
                level.Host = host;
                host.SpriteCleanup();
                _level = level;
                level.Theme = Theme;
                Theme.Level = level;

                try
                {
                    //get the list of room decks
                    List<List<Room>> decks = Theme.RoomDecks;

                    //build it centered in a potential space of 800x800
                    _matrix = new BitMatrix(MAX_DUNGEON_SIZE, MAX_DUNGEON_SIZE);

                    //add an entrance
                    Room entrance = Theme.GenerateEntrance();
                    entrance.Level = level;
                    entrance.X = MAX_DUNGEON_SIZE / 2;
                    entrance.Y = MAX_DUNGEON_SIZE / 2;
                    ApplyRoomShapeToMatrix(entrance);
                    level.Rooms.Add(entrance);

                    //spread out groups of rooms
                    foreach (List<Room> deck in decks)
                    {
                        int x = MAX_DUNGEON_SIZE / 2 + Globals.RandomIntFromTo(-50, 50);
                        int y = MAX_DUNGEON_SIZE / 2 + Globals.RandomIntFromTo(-50, 50);
                        foreach (Room room in deck)
                        {
                            int space = STARTING_SPACE;
                            do
                            {
                                room.X = x + Globals.RandomIntFromTo(-space, space);
                                room.Y = y + Globals.RandomIntFromTo(-space, space);
                                space++;
                                if (space > 200)
                                {
                                    success = false;
                                    break;
                                }
                            }
                            while ((!RoomInBounds(room)) || RoomOverlaps(room));
                            if (!success)
                                break;

                            ApplyRoomShapeToMatrix(room);
                            level.Rooms.Add(room);
                            x = room.CenterX;
                            y = room.CenterY;
                        }
                    }

                    //keep building corridors until the objective room is attached to the entrance
                    RandomList<Room> originalRooms = new RandomList<Room>();
                    foreach (Room r in level.Rooms)
                        originalRooms.Add(r);
                    Room firstRoom = originalRooms[0];

                    //create inter-zone routes
                    foreach (List<Room> deck in decks)
                    {
                        for (int i = 0; i < deck.Count - 1; i++)
                        {
                            Room room1 = deck[i];
                            Room room2 = deck[i + 1];
                            BuildRoute(room1, room2);
                        }

                        //then connect first room in each group to entrance
                        Room zoneRoom = deck[0];
                        if (zoneRoom != firstRoom)
                            BuildRoute(firstRoom, zoneRoom);
                    }
                    
                    //build some random routes for fun
                    int randomRoutes = Globals.RandomIntFromTo(1, 4);
                    while (randomRoutes > 0)
                    {
                        Room room1 = originalRooms.RandomValue;
                        Room room2 = originalRooms.RandomValue;
                        if (room1 != room2)
                            BuildRoute(room1, room2);
                        randomRoutes--;
                    }

                    //we now have the rooms layed out so we can get the overall dimensions
                    int left = level.Left;
                    int top = level.Top;

                    //offset all rooms to align to the left and top, leaving 1 cell for outer walls
                    foreach (Room room in level.Rooms)
                    {
                        room.X = (room.X - left) + 1;
                        room.Y = (room.Y - top) + 1;
                    }

                    //create terrain map
                    level.GenerateTerrainMap();

                    //room post processing
                    foreach (Room room in level.Rooms)
                    {
                        room.PostProcessing();
                        if (room.Faction != null)
                            room.Faction.PostProcessing(room);
                    }

                    //let rooms make stuff
                    Theme.PostProcessingPreCorridorRemoval();

                    //remove corridor room instances, they are carved into map
                    level.Rooms.Clear();
                    foreach (Room room in originalRooms)
                        level.Rooms.Add(room);

                    //theme post-processing
                    Theme.PostProcessingPostCorridorRemoval();

                    //clear matrix memory
                    _matrix = null;

                    //place party
                    level.PlacePartyInEntrance();
                }
                catch
                {
                    success = false;
                    _matrix = null;
                }

                if (level.Rooms.Count < 2)
                    success = false;
            }

            Dungeon.Levels.Add(level);
            return level;
        }

        //apply a room to the matrix
        private void ApplyRoomShapeToMatrix(Room room)
        {
            for (int y = 0; y < room.Height; y++)
                for (int x = 0; x < room.Width; x++)
                    if (room.Matrix.Get(x, y) != Globals.TERRAIN_WALL)
                        _matrix.Set(x + room.X, y + room.Y, true);
        }

        //is this room in bounds?
        private bool RoomInBounds(Room room)
        {
            if (room.Left < 0 || room.Right >= MAX_DUNGEON_SIZE)
                return false;
            if (room.Top < 0 || room.Bottom >= MAX_DUNGEON_SIZE)
                return false;
            return true;
        }

        //does this room overlap another room?
        private bool RoomOverlaps(Room room)
        {
            for (int y = 0; y < room.Height; y++)
                for (int x = 0; x < room.Width; x++)
                    if (room.Matrix.Get(x, y) != Globals.TERRAIN_WALL)
                        if (_matrix.Get(x + room.X, y + room.Y))
                            return true;

            //ensure that there is at least 1 space buffer around room
            if (!BorderIsClear(room, 0, 0, 1, 0, 0, -1))
                return true;
            if (!BorderIsClear(room, 0, room.Height - 1, 1, 0, 0, 1))
                return true;
            if (!BorderIsClear(room, 0, 0, 0, 1, -1, 0))
                return true;
            if (!BorderIsClear(room, room.Width - 1, 0, 0, 1, 1, 0))
                return true;

            return false;
        }

        //check the border above/below a room
        private bool BorderIsClear(Room room, int x, int y, int xDir, int yDir, int xOffset, int yOffset)
        {
            while (x >= 0 && x < room.Matrix.Width && y >= 0 && y < room.Matrix.Height) 
            { 
                char c = room.Matrix.Get(x, y);
                if (c != Globals.TERRAIN_WALL)
                { 
                    int testX = x + room.X + xOffset; 
                    int testY = y + room.Y + yOffset;
                    if (_matrix.Get(testX, testY))
                        return false;
                } 
                x += xDir; 
                y += yDir; 
            }
            return true;
        }

        //build a route from one room to another
        private void BuildRoute(Room roomFrom, Room roomTo)
        {
            Room prevRoom = roomTo;
            SCGDirection prevDirection = SCGDirection.Down;
            int cx = roomTo.CenterX;
            int cy = roomTo.CenterY;
            bool connected = false;
            do
            {
                //what direction should the corridor go?
                SCGDirection dir = SCGDirection.Up;
                int diffX = Math.Abs(cx - roomFrom.X);
                int diffY = Math.Abs(cy - roomFrom.Y);
                if (diffX > diffY)
                {
                    if (roomFrom.X < cx)
                        dir = SCGDirection.Left;
                    else
                        dir = SCGDirection.Right;
                }
                else
                {
                    if (roomFrom.Y < cy)
                        dir = SCGDirection.Up;
                    else
                        dir = SCGDirection.Down;
                }

                Room corridor = Theme.GenerateCorridor();

                if (dir == SCGDirection.Right)
                    corridor.Twist();
                else if (dir == SCGDirection.Down)
                    corridor.Twist(2);
                else if (dir == SCGDirection.Left)
                    corridor.Twist(3);

                corridor.X = cx;
                corridor.Y = cy;

                switch (dir)
                {
                    case SCGDirection.Up:
                        {
                            switch (prevDirection)
                            {
                                case SCGDirection.Up:
                                    {
                                        corridor.Y -= corridor.Height - 1;
                                        if (corridor.UseConnectorPoints)
                                            corridor.X -= corridor.StartConnector;
                                        break;
                                    }
                                case SCGDirection.Right:
                                    {
                                        corridor.Y -= corridor.Height - 2;
                                        if (corridor.UseConnectorPoints)
                                        {
                                            corridor.Y--;
                                            corridor.X -= corridor.StartConnector;
                                        }
                                        break;
                                    }
                                case SCGDirection.Down:
                                    {
                                        corridor.Y -= corridor.Height - 1;
                                        corridor.X--;
                                        if (corridor.UseConnectorPoints)
                                        {
                                            corridor.X++;
                                            corridor.X -= corridor.StartConnector;
                                        }
                                        break;
                                    }
                                case SCGDirection.Left:
                                    {
                                        corridor.X -= corridor.Width - 1;
                                        corridor.Y -= corridor.Height - 1;
                                        if (corridor.UseConnectorPoints)
                                            corridor.X += corridor.StartConnectorFlipped;
                                        break;
                                    }
                            }
                            cx = corridor.Left + corridor.EndConnector;
                            cy = corridor.Top;
                            break;
                        }
                    case SCGDirection.Right:
                        {
                            switch (prevDirection)
                            {
                                case SCGDirection.Up:
                                {
                                    corridor.Y -= corridor.Height - 1;
                                    if (corridor.UseConnectorPoints)
                                        corridor.Y += corridor.StartConnectorFlippedByHeight;
                                    break;
                                }
                                case SCGDirection.Right:
                                {
                                    if (corridor.UseConnectorPoints)
                                        corridor.Y -= corridor.StartConnector;
                                    break;
                                }
                                case SCGDirection.Down:
                                {
                                    corridor.X--;
                                    if (corridor.UseConnectorPoints)
                                    {
                                        corridor.X++;
                                        corridor.Y -= corridor.StartConnector;
                                    }
                                    break;
                                }
                                case SCGDirection.Left:
                                {
                                    corridor.Y--;
                                    if (corridor.UseConnectorPoints)
                                    {
                                        corridor.Y++;
                                        corridor.Y -= corridor.StartConnector;
                                    }
                                    break;
                                }
                            }
                            cx = corridor.Right;
                            cy = corridor.Top + corridor.EndConnector;
                            break;
                        }
                    case SCGDirection.Down:
                        {
                            switch (prevDirection)
                            {
                                case SCGDirection.Up:
                                    {
                                        if (corridor.UseConnectorPoints)
                                            corridor.X -= corridor.StartConnectorFlipped;
                                        break;
                                    }
                                case SCGDirection.Right:
                                    {
                                        if (corridor.UseConnectorPoints)
                                            corridor.X -= corridor.StartConnectorFlipped;
                                        break;
                                    }
                                case SCGDirection.Down:
                                    {
                                        corridor.X--;
                                        if (corridor.UseConnectorPoints)
                                        {
                                            corridor.X++;
                                            corridor.X += corridor.StartConnectorFlipped;
                                        }
                                        break;
                                    }
                                case SCGDirection.Left:
                                    {
                                        corridor.X -= corridor.Width - 1;
                                        corridor.Y--;
                                        if (corridor.UseConnectorPoints)
                                        {
                                            corridor.Y++;
                                            corridor.X += corridor.StartConnector;
                                        }
                                        break;
                                    }
                            }
                            cx = corridor.Right;
                            if (corridor.UseConnectorPoints)
                                cx = corridor.Right - corridor.EndConnector;
                            cy = corridor.Bottom;
                            break;
                        }
                    case SCGDirection.Left:
                        {
                            switch (prevDirection)
                            {
                                case SCGDirection.Up:
                                    {
                                        corridor.Y -= corridor.Height - 1;
                                        corridor.X -= corridor.Width - 2;
                                        if (corridor.UseConnectorPoints)
                                        {
                                            corridor.X--;
                                            corridor.Y += corridor.StartConnector;
                                        }
                                        break;
                                    }
                                case SCGDirection.Right:
                                    {
                                        corridor.X -= corridor.Width - 1;
                                        if (corridor.UseConnectorPoints)
                                            corridor.Y -= corridor.StartConnectorFlippedByHeight;
                                        break;
                                    }
                                case SCGDirection.Down:
                                    {
                                        corridor.X -= corridor.Width - 1;
                                        if (corridor.UseConnectorPoints)
                                            corridor.Y -= corridor.StartConnectorFlippedByHeight;
                                        break;
                                    }
                                case SCGDirection.Left:
                                    {
                                        corridor.X -= corridor.Width - 1;
                                        corridor.Y--;
                                        if (corridor.UseConnectorPoints)
                                        {
                                            corridor.Y++;
                                            corridor.Y -= corridor.StartConnectorFlippedByHeight;
                                        }
                                        break;
                                    }
                            }
                            cx = corridor.Left;
                            cy = corridor.Bottom;
                            if (corridor.UseConnectorPoints)
                                cy = corridor.Top + (corridor.Height - corridor.EndConnector - 1);
                            break;
                        }
                }

                if (!corridor.Touches(prevRoom))
                {
                    throw new Exception("Rooms don't touch " + prevRoom + "," + corridor);
                }

                ApplyRoomShapeToMatrix(corridor);
                _level.Rooms.Add(corridor);

                connected = corridor.Touches(roomFrom);
                prevDirection = dir;
                prevRoom = corridor;
            }
            while (!connected);
        }
        
        //private members
        private DungeonLevel _level;
        private BitMatrix _matrix;
    }
}
